跳到主要内容

PyTorch 的 Conv 操作

Convolution 是什么?

在 PyTorch 中,卷积操作(Convolution)是深度学习中常用的一种操作,用于处理图像、文本等数据的特征提取。PyTorch 提供了丰富的函数和类来执行卷积操作,通常在 torch.nn 模块中找到。

卷积操作的基本思想是将一个滤波器(也称为卷积核或内核)在输入数据上滑动,执行点乘并求和的操作,从而在输出中生成特征图。这有助于捕获输入数据中的局部模式和特征。

以下是 PyTorch 中常见的卷积操作和相关概念:

  1. torch.nn.Conv2d 这是用于二维图像的标准卷积层。它使用一个或多个卷积核在输入特征图上执行卷积操作。你可以设置卷积核的数量、大小、步幅和填充等参数。
import torch
import torch.nn as nn

# 创建一个二维卷积层
conv_layer = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
  1. 输入特征图: 输入到卷积层的数据,通常表示为一个四维张量,具有形状 (batch_size, channels, height, width)

  2. 卷积核: 也称为滤波器或内核,是一个小的权重张量,通过卷积操作在输入上滑动。卷积核的大小由 kernel_size 决定。

  3. 输出特征图: 卷积操作的结果,具有形状 (batch_size, num_filters, new_height, new_width),其中 num_filters 是卷积核的数量,new_heightnew_width 取决于步幅和填充。

  4. 步幅(stride): 指定卷积核在输入特征图上滑动的步长。

  5. 填充(padding): 在输入特征图的边缘添加额外的像素,以控制输出特征图的大小。

卷积操作的过程可以总结为:对于每个位置,将卷积核与输入数据中对应位置的数据进行点乘,然后对所有点乘结果求和,得到输出特征图中对应位置的值。

在 PyTorch 中,你可以创建卷积层,将输入数据传递给卷积层,然后获取输出特征图。卷积操作是深度学习中重要的基础操作,广泛用于图像识别、图像生成、目标检测等任务中。

生成无缝图

下面是 asymmetric-tiling-sd-webui 插件的源码,里面有一段就是通过操作卷积层来生成无缝图的逻辑

# [Private]
# 遍历模型中的所有 “Conv2D” 层,并将它们修补为使用所请求的非对称平铺模式。
def __hijackConv2DMethods(self, tileX: bool, tileY: bool, startStep: int, stopStep: int):
for layer in modules.sd_hijack.model_hijack.layers:
if type(layer) == Conv2d:
layer.padding_modeX = 'circular' if tileX else 'constant'
layer.padding_modeY = 'circular' if tileY else 'constant'
layer.paddingX = (layer._reversed_padding_repeated_twice[0], layer._reversed_padding_repeated_twice[1], 0, 0)
layer.paddingY = (0, 0, layer._reversed_padding_repeated_twice[2], layer._reversed_padding_repeated_twice[3])
layer.paddingStartStep = startStep
layer.paddingStopStep = stopStep
layer._conv_forward = Script.__replacementConv2DConvForward.__get__(layer, Conv2d)


# [Private]
# A replacement for the Conv2d._conv_forward method that pads axes asymmetrically.
# This replacement method performs the same operation (as of torch v1.12.1+cu113), but it pads the X and Y axes separately based on the members
# padding_modeX (string, either 'circular' or 'constant')
# padding_modeY (string, either 'circular' or 'constant')
# paddingX (tuple, cached copy of _reversed_padding_repeated_twice with the last two values zeroed)
# paddingY (tuple, cached copy of _reversed_padding_repeated_twice with the first two values zeroed)
def __replacementConv2DConvForward(self, input: Tensor, weight: Tensor, bias: Optional[Tensor]):
step = modules.shared.state.sampling_step
if ((self.paddingStartStep < 0 or step >= self.paddingStartStep) and (self.paddingStopStep < 0 or step <= self.paddingStopStep)):
working = F.pad(input, self.paddingX, mode=self.padding_modeX)
working = F.pad(working, self.paddingY, mode=self.padding_modeY)
else:
working = F.pad(input, self.paddingX, mode='constant')
working = F.pad(working, self.paddingY, mode='constant')
return F.conv2d(working, weight, bias, self.stride, _pair(0), self.dilation, self.groups)

下面对上面的代码详细说明

1、首先是这块代码

layer.padding_modeX = 'circular' if tileX else 'constant'
layer.padding_modeY = 'circular' if tileY else 'constant'

在 PyTorch 中,padding_mode 是卷积层(如 nn.Conv2d)的一个参数,用于指定填充的方式。填充是卷积操作中的一个重要概念,它指在输入特征图的边缘添加额外的像素,以控制输出特征图的大小。

这个 padding_mode 参数可以接受以下几个字符串值:

  1. "zeros"(默认值): 这表示在输入特征图的边缘添加零填充。这是最常见的填充方式,它在输入的边缘添加零值像素。
  2. "reflect": 这表示在输入特征图的边缘添加镜像填充。它会将输入的边缘像素沿边缘进行镜像复制。
  3. "replicate": 这表示在输入特征图的边缘添加复制填充。它会将输入的边缘像素进行复制。
  4. "circular": 这表示在输入特征图的边缘添加循环填充。它会将输入的边缘像素循环地复制。

这些不同的填充方式在不同的情况下可能有用,例如,"reflect" 填充可以在处理边缘像素时减少边缘效应,"replicate" 填充可以在一些图像处理任务中保持边缘的一致性。

你可以在创建卷积层对象时,通过设置 padding_mode 参数来指定所需的填充方式。例如:

import torch.nn as nn

conv_layer = nn.Conv2d(in_channels, out_channels, kernel_size, padding=1, padding_mode='reflect')

在这个例子中,卷积层将使用 "reflect" 填充方式,并在输入特征图的边缘添加镜像填充。

2、然后是这个填充的范围

layer.paddingX = (layer._reversed_padding_repeated_twice[0], layer._reversed_padding_repeated_twice[1], 0, 0)
layer.paddingY = (0, 0, layer._reversed_padding_repeated_twice[2], layer._reversed_padding_repeated_twice[3])

这段代码是将 _reversed_padding_repeated_twice 这个属性的值用来设置 paddingXpaddingY 这两个属性。这些属性主要用于控制卷积层的填充(padding)操作。

提示

这个 self._reversed_padding_repeated_twice 会将 padding 格式变成 2 的倍数大小的整形元组。

例如:padding=1,那么 self._reversed_padding_repeated_twice 就会等于 (1,1); 如果 padding=(2,2),那么 self._reversed_padding_repeated_twice 就会等于 (2, 2, 2, 2)

这段代码通过将 _reversed_padding_repeated_twice 属性中的值按照一定规则赋值给 paddingXpaddingY

具体来说:

  • paddingX 的左填充取 _reversed_padding_repeated_twice 的前两个值,右填充为零。
  • paddingY 的上填充为零,下填充取 _reversed_padding_repeated_twice 的后两个值。

这样设置之后,卷积层在进行前向传播的时候将会根据这些填充值在输入数据的 X 轴和 Y 轴方向上进行填充。这样的操作在某些特定的情况下可能是有用的,例如对称或非对称填充等。

3、然后是下面替换的 _conv_forward 函数

layer._conv_forward = Script.__replacementConv2DConvForward.__get__(layer, Conv2d)

在 PyTorch 的源代码中,_conv_forward 是用于执行卷积操作的内部函数。这个函数是在卷积层(如 nn.Conv2d)的实现中使用的,用于计算卷积操作的前向传播。它处理输入数据和卷积核,执行点乘和求和操作,以生成输出特征图。

上面那段替换的代码是一个用于 PyTorch 的 Conv2d 类的方法重写,用于执行卷积操作的前向传播。从注释和代码中可以看出,这个方法重写主要是针对填充(padding)的处理方式进行了修改,使得 X 和 Y 轴的填充可以分别使用不同的填充模式(padding_modeX 和 padding_modeY)和填充值(paddingX 和 paddingY)。

让我们逐步解释这个方法的不同部分:

  1. step 是一个参数,表示当前的步骤,用于控制是否应用特定填充方式。paddingStartSteppaddingStopStep 是两个用于控制填充应用范围的参数。(这里则是匹配到需要填充的区域,即被填充的内容)

  2. 首先,根据 paddingXpaddingY 参数,以及根据 padding_modeXpadding_modeY 参数(即上面设置的填充模式),对输入数据 input 进行不同的填充。如果当前步骤在指定的填充范围内,将使用相应的填充模式,否则将使用常数填充模式。

  3. 在进行填充之后,通过 F.conv2d 函数执行卷积操作。F.conv2d 是 PyTorch 提供的卷积操作函数,它接受输入数据、卷积核权重 weight,可选的偏置 bias,以及其他卷积相关的参数,如步幅、膨胀等。

总结来说,这个方法的主要目的是对卷积操作的前向传播进行了改写,使得填充方式可以在 X 和 Y 轴上分别使用不同的模式和值。这可能在某些特定场景下对填充的处理提供了更大的灵活性。